#include <sdk.h>
#include <wx/progdlg.h>
#include <M_Code_StyledTextCtrl.h>
#include <encodingdetector.h>
#include <searchresultslog.h>
#include "coderefactoring.h"
#include "nativeparser.h"

#define CC_CODEREFACTORING_DEBUG_OUTPUT 0

class ScopeDialog : public wxDialog {
  public:
    ScopeDialog( wxWindow* parent, const wxString& title ) :
      wxDialog( parent, wxID_ANY, title ) {
      wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
      wxBoxSizer* infoSizer = new wxBoxSizer( wxHORIZONTAL );
      wxString findImgFile = ConfigManager::GetDataFolder() + _T( "/images/filefind.png" );
      wxStaticBitmap* findIco = new wxStaticBitmap( this, wxID_ANY, wxBitmap( M_Code_LoadBitmap( findImgFile, wxBITMAP_TYPE_PNG ) ) );
      infoSizer->Add( findIco, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5 );
      wxStaticText* scopeText = new wxStaticText( this, wxID_ANY, _T( "请选择搜索范围" ) );
      infoSizer->Add( scopeText, 1, wxALL | wxALIGN_CENTER_VERTICAL, wxDLG_UNIT( this, wxSize( 5, 0 ) ).GetWidth() );
      sizer->Add( infoSizer, 1, wxALL | wxALIGN_CENTER_HORIZONTAL, 5 );
      wxBoxSizer* btnSizer = new wxBoxSizer( wxHORIZONTAL );
      m_OpenFiles = new wxButton( this, ID_OPEN_FILES, _T( "打开文件" ), wxDefaultPosition, wxDefaultSize, 0,
                                  wxDefaultValidator, _T( "ID_OPEN_FILES" ) );
      m_OpenFiles->SetDefault();
      btnSizer->Add( m_OpenFiles, 1, wxALL | wxALIGN_CENTER_VERTICAL, 5 );
      m_ProjectFiles = new wxButton( this, ID_PROJECT_FILES, _T( "项目文件" ), wxDefaultPosition,
                                     wxDefaultSize, 0, wxDefaultValidator, _T( "ID_PROJECT_FILES" ) );
      btnSizer->Add( m_ProjectFiles, 1, wxALL | wxALIGN_CENTER_VERTICAL, 5 );
      sizer->Add( btnSizer, 1, wxBOTTOM | wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, 5 );
      SetSizer( sizer );
      sizer->Fit( this );
      sizer->SetSizeHints( this );
      Center();
      Connect( ID_OPEN_FILES, wxEVT_COMMAND_BUTTON_CLICKED, ( wxObjectEventFunction )&ScopeDialog::OnOpenFilesClick );
      Connect( ID_PROJECT_FILES, wxEVT_COMMAND_BUTTON_CLICKED, ( wxObjectEventFunction )&ScopeDialog::OnProjectFilesClick );
      Connect( wxID_ANY, wxEVT_CLOSE_WINDOW, ( wxObjectEventFunction )&ScopeDialog::OnClose );
    }
  public:
    static const long ID_OPEN_FILES;
    static const long ID_PROJECT_FILES;
  private:
    void OnClose( M_Code_unused wxCloseEvent& event ) { EndDialog( wxID_CLOSE ); }
    void OnOpenFilesClick( M_Code_unused wxCommandEvent& event ) { EndDialog( ID_OPEN_FILES );}
    void OnProjectFilesClick( M_Code_unused wxCommandEvent& event ) { EndDialog( ID_PROJECT_FILES ); }
    wxButton* m_OpenFiles;
    wxButton* m_ProjectFiles;
};

const long ScopeDialog::ID_OPEN_FILES = wxNewId();
const long ScopeDialog::ID_PROJECT_FILES = wxNewId();

CodeRefactoring::CodeRefactoring( NativeParser& np ) :
  m_NativeParser( np ) {
}

CodeRefactoring::~CodeRefactoring() {
}

wxString CodeRefactoring::GetSymbolUnderCursor() {
  EditorManager* edMan = Manager::Get()->GetEditorManager();
  M_Code_Editor* editor = edMan->GetBuiltinActiveEditor();
  if( !editor ) {
    return wxEmptyString;
  }
  M_Code_StyledTextCtrl* control = editor->GetControl();
  const int style = control->GetStyleAt( control->GetCurrentPos() );
  if( control->IsString( style ) || control->IsComment( style ) ) {
    return wxEmptyString;
  }
  if( !m_NativeParser.GetParser().Done() ) {
    wxString msg( _T( "分析器仍在分析文件." ) );
    msging( msg, _T( "代码重构" ), wxOK | wxICON_WARNING );
    msg += m_NativeParser.GetParser().NotDoneReason();
    CCLogger::Get()->DebugLog( msg );
    return wxEmptyString;
  }
  const int pos = editor->GetControl()->GetCurrentPos();
  const int start = editor->GetControl()->WordStartPosition( pos, true );
  const int end = editor->GetControl()->WordEndPosition( pos, true );
  return editor->GetControl()->GetTextRange( start, end );
}

bool CodeRefactoring::Parse() {
  M_Code_Editor* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !editor ) {
    return false;
  }
  const wxString targetText = GetSymbolUnderCursor();
  if( targetText.IsEmpty() ) {
    return false;
  }
  TokenIdxSet targetResult;
  const int endOfWord = editor->GetControl()->WordEndPosition( editor->GetControl()->GetCurrentPos(), true );
  m_NativeParser.MarkItemsByAI( targetResult, true, false, true, endOfWord );
  if( targetResult.empty() ) {
    msging( _T( "在光标下找不到符号!" ), _T( "代码重构" ), wxOK | wxICON_WARNING );
    return false;
  }
  bool isLocalVariable = false;
  TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
  CC_LOCKER_TRACK_TT_MTX_LOCK( s_TokenTreeMutex )
  const Token* token = tree->at( *targetResult.begin() );
  if( token ) {
    const Token* parent = tree->at( token->m_ParentIndex );
    if( parent && parent->m_TokenKind == tkFunction ) {
      isLocalVariable = true;
    }
  }
  CC_LOCKER_TRACK_TT_MTX_UNLOCK( s_TokenTreeMutex )
  wxArrayString files;
  M_Code_Project* project = m_NativeParser.GetProjectByEditor( editor );
  if( isLocalVariable || !project ) {
    files.Add( editor->GetFilename() );
  } else {
    ScopeDialog scopeDlg( Manager::Get()->GetAppWindow(), _T( "代码重构" ) );
    const int ret = scopeDlg.ShowModal();
    if( ret == ScopeDialog::ID_OPEN_FILES ) {
      GetOpenedFiles( files );
    } else if( ret == ScopeDialog::ID_PROJECT_FILES ) {
      GetAllProjectFiles( files, project );
    } else
    { return false; }
  }
  if( files.IsEmpty() ) {
    return false;
  }
  size_t count = SearchInFiles( files, targetText );
  if( count ) {
    count = VerifyResult( targetResult, targetText, isLocalVariable );
  }
  return count != 0;
}

void CodeRefactoring::FindReferences() {
  if( Parse() ) {
    DoFindReferences();
  }
}

void CodeRefactoring::RenameSymbols() {
  const wxString targetText = GetSymbolUnderCursor();
  if( targetText.IsEmpty() ) {
    return;
  }
  wxString replaceText = cbGetTextFromUser( _T( "重命名光标下的符号" ), _T( "代码重构" ), targetText, Manager::Get()->GetAppWindow() );
  if( !replaceText.IsEmpty() && replaceText != targetText && Parse() ) {
    DoRenameSymbols( targetText, replaceText );
    DoFindReferences();
  }
}

size_t CodeRefactoring::SearchInFiles( const wxArrayString& files, const wxString& targetText ) {
  EditorManager* edMan = Manager::Get()->GetEditorManager();
  m_SearchDataMap.clear();
  wxWindow* parent = edMan->GetBuiltinActiveEditor()->GetParent();
  M_Code_StyledTextCtrl* control = new M_Code_StyledTextCtrl( parent, wxID_ANY, wxDefaultPosition, wxSize( 0, 0 ) );
  control->Show( false );
  wxProgressDialog* progress = new wxProgressDialog( _T( "代码重构" ), _T( "正在项目内搜索，请稍候..." ), files.GetCount(),
      Manager::Get()->GetAppWindow(), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT );
  PlaceWindow( progress );
  for( size_t i = 0; i < files.GetCount(); ++i ) {
    if( !progress->Update( i ) ) {
      break;
    }
    M_Code_Editor* ed = edMan->IsBuiltinOpen( files[i] );
    if( ed ) {
      control->SetText( ed->GetControl()->GetText() );
    } else {
      EncodingDetector detector( files[i] );
      if( !detector.IsOK() ) {
        continue;
      }
      control->SetText( detector.GetWxStr() );
    }
    Find( control, files[i], targetText );
  }
  delete control;
  delete progress;
  return m_SearchDataMap.size();
}

size_t CodeRefactoring::VerifyResult( const TokenIdxSet& targetResult, const wxString& targetText, bool isLocalVariable ) {
  EditorManager* edMan = Manager::Get()->GetEditorManager();
  M_Code_Editor* editor = edMan->GetBuiltinActiveEditor();
  if( !editor ) {
    return 0;
  }
  const Token* parentOfLocalVariable = nullptr;
  if( isLocalVariable ) {
    TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
    CC_LOCKER_TRACK_TT_MTX_LOCK( s_TokenTreeMutex )
    const Token* token = tree->at( *targetResult.begin() );
    parentOfLocalVariable = tree->at( token->m_ParentIndex );
    CC_LOCKER_TRACK_TT_MTX_UNLOCK( s_TokenTreeMutex )
  }
  M_Code_StyledTextCtrl* control = new M_Code_StyledTextCtrl( editor->GetParent(), wxID_ANY, wxDefaultPosition, wxSize( 0, 0 ) );
  control->Show( false );
  M_Code_Editor::ApplyStyles( control );
  EditorColourSet edColSet;
  size_t totalCount = 0;
  for( SearchDataMap::const_iterator it = m_SearchDataMap.begin(); it != m_SearchDataMap.end(); ++it ) {
    totalCount += it->second.size();
  }
  wxProgressDialog* progress = new wxProgressDialog( _T( "代码重构" ), _T( "正在验证结果，请稍候..." ),
      totalCount, Manager::Get()->GetAppWindow(), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT );
  PlaceWindow( progress );
  size_t task = totalCount;
  TokenIdxSet result;
  bool userBreak = false;
  for( SearchDataMap::iterator it = m_SearchDataMap.begin(); it != m_SearchDataMap.end(); ) {
    M_Code_Editor* ed = edMan->IsBuiltinOpen( it->first );
    if( ed ) {
      control->SetText( ed->GetControl()->GetText() );
    } else {
      EncodingDetector detector( it->first );
      if( !detector.IsOK() ) {
        task -= it->second.size();
        m_SearchDataMap.erase( it++ );
        continue;
      }
      control->SetText( detector.GetWxStr() );
    }
    edColSet.Apply( editor->GetLanguage(), control );
    ccSearchData searchData = { control, it->first };
    for( SearchDataList::iterator itList = it->second.begin(); itList != it->second.end(); ) {
      if( !progress->Update( totalCount - ( --task ) ) ) {
        userBreak = true;
        break;
      }
      const int style = control->GetStyleAt( itList->pos );
      if( control->IsString( style ) || control->IsComment( style ) ) {
        it->second.erase( itList++ );
        continue;
      }
      const int endOfWord = itList->pos + targetText.Len();
      control->GotoPos( endOfWord );
      m_NativeParser.MarkItemsByAI( &searchData, result, true, false, true, endOfWord );
      if( result.empty() ) {
        it->second.erase( itList++ );
        continue;
      }
      TokenIdxSet::const_iterator findIter = targetResult.begin();
      for( ; findIter != targetResult.end(); ++findIter ) {
        if( result.find( *findIter ) != result.end() ) {
          break;
        }
      }
      if( findIter == targetResult.end() ) {
        it->second.erase( itList++ );
      } else {
        if( isLocalVariable ) {
          bool do_continue = false;
          TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
          CC_LOCKER_TRACK_TT_MTX_LOCK( s_TokenTreeMutex )
          const Token* token = tree->at( *findIter );
          if( token ) {
            const Token* parent = tree->at( token->m_ParentIndex );
            if( parent != parentOfLocalVariable ) {
              it->second.erase( itList++ );
              do_continue = true;
            }
          }
          CC_LOCKER_TRACK_TT_MTX_UNLOCK( s_TokenTreeMutex )
          if( do_continue ) {
            continue;
          }
        }
        ++itList;
      }
    }
    if( it->second.empty() ) {
      m_SearchDataMap.erase( it++ );
    } else
    { ++it; }
    if( userBreak ) {
      break;
    }
  }
  delete control;
  delete progress;
  return m_SearchDataMap.size();
}

void CodeRefactoring::Find( M_Code_StyledTextCtrl* control, const wxString& file, const wxString& target ) {
  const int end = control->GetLength();
  int start = 0;
  for( ;; ) {
    int lengthFound;
    int pos = control->FindText( start, end, target, wxSCI_FIND_WHOLEWORD | wxSCI_FIND_MATCHCASE, &lengthFound );
    if( pos != wxSCI_INVALID_POSITION ) {
      start = pos + lengthFound;
      const int line = control->LineFromPosition( pos );
      wxString text = control->GetLine( line ).Trim( true ).Trim( false );
      m_SearchDataMap[file].push_back( crSearchData( pos, line + 1, text ) );
    } else
    { break; }
  }
}

void CodeRefactoring::DoFindReferences() {
  M_Code_Editor* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !editor ) {
    return;
  }
  cbSearchResultsLog* searchLog = Manager::Get()->GetSearchResultLogger();
  if( !searchLog ) {
    return;
  }
  const wxString focusFile = editor->GetFilename();
  const int focusLine = editor->GetControl()->GetCurrentLine() + 1;
  wxFileName fn( focusFile );
  const wxString basePath( fn.GetPath() );
  size_t index = 0;
  size_t focusIndex = 0;
  searchLog->Clear();
  searchLog->SetBasePath( basePath );
  for( SearchDataMap::iterator it = m_SearchDataMap.begin(); it != m_SearchDataMap.end(); ++it ) {
    for( SearchDataList::iterator itList = it->second.begin(); itList != it->second.end(); ++itList ) {
      if( it->first == focusFile && itList->line == focusLine ) {
        focusIndex = index;
      }
      wxArrayString values;
      wxFileName curFn( it->first );
      curFn.MakeRelativeTo( basePath );
      values.Add( curFn.GetFullPath() );
      values.Add( F( _T( "%d" ), itList->line ) );
      values.Add( itList->text );
      searchLog->Append( values, Logger::info );
      ++index;
    }
  }
  if( Manager::Get()->GetConfigManager( _T( "message_manager" ) )->ReadBool( _T( "/auto_show_search" ), true ) ) {
    CodeBlocksLogEvent evtSwitch( cbEVT_SWITCH_TO_LOG_WINDOW, searchLog );
    CodeBlocksLogEvent evtShow( cbEVT_SHOW_LOG_MANAGER );
    Manager::Get()->ProcessEvent( evtSwitch );
    Manager::Get()->ProcessEvent( evtShow );
  }
  searchLog->FocusEntry( focusIndex );
}

void CodeRefactoring::DoRenameSymbols( const wxString& targetText, const wxString& replaceText ) {
  EditorManager* edMan = Manager::Get()->GetEditorManager();
  M_Code_Editor* editor = edMan->GetBuiltinActiveEditor();
  if( !editor ) {
    return;
  }
  M_Code_Project* project = m_NativeParser.GetProjectByEditor( editor );
  for( SearchDataMap::iterator it = m_SearchDataMap.begin(); it != m_SearchDataMap.end(); ++it ) {
    M_Code_Editor* ed = edMan->IsBuiltinOpen( it->first );
    if( !ed ) {
      ProjectFile* pf = project ? project->GetFileByFilename( it->first ) : 0;
      ed = edMan->Open( it->first, it->second.front().pos, pf );
    }
    M_Code_StyledTextCtrl* control = ed->GetControl();
    control->BeginUndoAction();
    for( SearchDataList::reverse_iterator rIter = it->second.rbegin(); rIter != it->second.rend(); ++rIter ) {
      control->SetTargetStart( rIter->pos );
      control->SetTargetEnd( rIter->pos + targetText.Len() );
      control->ReplaceTarget( replaceText );
      rIter->text.Replace( targetText, replaceText );
    }
    control->EndUndoAction();
  }
}

void CodeRefactoring::GetAllProjectFiles( wxArrayString& files, M_Code_Project* project ) {
  if( !project ) {
    return;
  }
  for( FilesList::const_iterator it = project->GetFilesList().begin();
       it != project->GetFilesList().end(); ++it ) {
    ProjectFile* pf = *it;
    if( !pf ) {
      continue;
    }
    ParserCommon::EFileType ft = ParserCommon::FileType( pf->relativeFilename );
    if( ft != ParserCommon::ftOther ) {
      files.Add( pf->file.GetFullPath() );
    }
  }
}

void CodeRefactoring::GetOpenedFiles( wxArrayString& files ) {
  EditorManager* edMan = Manager::Get()->GetEditorManager();
  if( edMan ) {
    for( int i = 0; i < edMan->GetEditorsCount(); ++i ) {
      files.Add( edMan->GetEditor( i )->GetFilename() );
    }
  }
}
